package com.bawei.lib_common.widget


import android.content.Context
import android.text.Layout
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.TextPaint
import android.text.TextUtils
import android.text.style.CharacterStyle
import android.text.style.ClickableSpan
import android.util.AttributeSet
import android.view.View
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.ContextCompat
import com.bawei.lib_common.R

import java.util.Collections
import kotlin.math.max


/**
 *作者：lxy
 *功能:
 *时间：2025/6/3
 */
class EllipsizeTextView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
    AppCompatTextView(context!!, attrs) {
    private var mEllipsizeText: CharSequence? = null
    private var mOriginText: CharSequence? = null

    // private int mEllipsizeIndex;
    private var mMaxLines = 0
    private val mEndText = "" //""全文";
    private var mIsExactlyMode = false
    private var mEnableUpdateOriginText = true
    private var mOnEndTextClickListener: OnEndTextClickListener? = null

    init {
        if (mEllipsizeText == null) {
            mEllipsizeText = DEFAULT_ELLIPSIZE_TEXT
        }
    }

    override fun setMaxLines(maxLines: Int) {
        if (mMaxLines != maxLines) {
            super.setMaxLines(maxLines)
            this.mMaxLines = maxLines
        }
    }

    fun setOnEndTextClick(l: OnEndTextClickListener?) {
        this.mOnEndTextClickListener = l
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        text = mOriginText
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        try {
            mIsExactlyMode = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
            val layout: Layout? = layout
            if (layout != null) {
                if (isExceedMaxLine(layout) || isOutOfBounds(layout)) {
                    adjustEllipsizeEndText(layout)
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun setText(text: CharSequence?, type: BufferType?) {
        if (mEnableUpdateOriginText) {
            mOriginText = text
        }

        super.setText(text, type)

        if (mIsExactlyMode) {
            requestLayout()
        }
    }

    private fun isExceedMaxLine(layout: Layout): Boolean {
        return layout.getLineCount() > mMaxLines && mMaxLines > 0
    }

    private fun isOutOfBounds(layout: Layout): Boolean {
        return layout.getHeight() > measuredHeight - paddingBottom - paddingTop
    }

    private fun adjustEllipsizeEndText(layout: Layout) {
        val originText = mOriginText


        val width: Int = layout.getWidth() - paddingLeft - paddingRight
        val maxLineCount =
            max(1.0, computeMaxLineCount(layout).toDouble()).toInt().toInt()
        val lastLineWidth = layout.getLineWidth(maxLineCount - 1) as Int
        var mLastCharacterIndex: Int = layout.getLineEnd(maxLineCount - 1)
        while (originText.toString()[mLastCharacterIndex - 1] == '\n') {
            // remove any \n that are present, because the read more link will not be shown otherwise
            mLastCharacterIndex = mLastCharacterIndex - 1
        }

        val suffixWidth = (Layout.getDesiredWidth(mEllipsizeText, paint)) as Int + 1

        mEnableUpdateOriginText = false
        if (lastLineWidth + suffixWidth > width) {
            val widthDiff = lastLineWidth + suffixWidth - width
            val removedCharacterCount = computeRemovedEllipsizeEndCharacterCount(
                widthDiff,
                originText!!.subSequence(0, mLastCharacterIndex)
            )

            text = originText.subSequence(0, mLastCharacterIndex - removedCharacterCount)
        } else {
            text = originText!!.subSequence(0, mLastCharacterIndex)
        }
        append(mEllipsizeText)
        val spannableStringBuilder = SpannableStringBuilder(mEndText)
        spannableStringBuilder.setSpan(object : ClickableSpan() {
            override fun onClick(view: View) {
                if (mOnEndTextClickListener != null) {
                    mOnEndTextClickListener!!.click()
                }
            }

            override fun updateDrawState(ds: TextPaint) {
                ds.color = ContextCompat.getColor(context, R.color.topic)
                ds.isUnderlineText = false
            }
        }, 0, mEndText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        append(spannableStringBuilder)

        mEnableUpdateOriginText = true
    }

    private fun computeMaxLineCount(layout: Layout): Int {
        val availableHeight = measuredHeight - paddingTop - paddingBottom
        for (i in 0 until layout.getLineCount()) {
            if (availableHeight < layout.getLineBottom(i)) {
                return i
            }
        }

        return layout.getLineCount()
    }

    private fun computeRemovedEllipsizeEndCharacterCount(widthDiff: Int, text: CharSequence): Int {
        if (TextUtils.isEmpty(text)) {
            return 0
        }

        val characterStyleRanges = computeCharacterStyleRanges(text)
        val textStr = text.toString()

        // prevent the subString from containing messy code when the given string contains emotion
        var characterIndex = text.length
        var codePointIndex = textStr.codePointCount(0, text.length)
        var currentRemovedWidth = 0

        while (codePointIndex > 0 && widthDiff > currentRemovedWidth) {
            codePointIndex--
            characterIndex = textStr.offsetByCodePoints(0, codePointIndex)

            // prevent the subString from containing messy code when the given string contains CharacterStyle
            val characterStyleRange =
                computeCharacterStyleRange(characterStyleRanges, characterIndex)
            if (characterStyleRange != null) {
                characterIndex = characterStyleRange.lower
                codePointIndex = textStr.codePointCount(0, characterIndex)
            }

            currentRemovedWidth = Layout.getDesiredWidth(
                text.subSequence(characterIndex, text.length),
                paint
            ).toInt()
        }

        return text.length - textStr.offsetByCodePoints(0, codePointIndex)
    }

    private fun computeCharacterStyleRange(
        characterStyleRanges: List<Range<Int>?>?,
        index: Int
    ): Range<Int>? {
        if (characterStyleRanges == null || characterStyleRanges.isEmpty()) {
            return null
        }

        for (characterStyleRange in characterStyleRanges) {
            if (characterStyleRange!!.contains(index)) {
                return characterStyleRange
            }
        }

        return null
    }

    private fun computeCharacterStyleRanges(text: CharSequence): List<Range<Int>?> {
        val ssb = SpannableStringBuilder.valueOf(text)
        val characterStyles = ssb.getSpans(
            0, ssb.length,
            CharacterStyle::class.java
        )

        if (characterStyles == null || characterStyles.size == 0) {
            return Collections.emptyList()
        }

        val ranges: MutableList<Range<Int>?> = ArrayList()
        for (characterStyle in characterStyles) {
            ranges.add(Range(ssb.getSpanStart(characterStyle), ssb.getSpanEnd(characterStyle)))
        }

        return ranges
    }

    /**
     * @param ellipsizeText causes words in the text that are longer than the view is wide
     * to be ellipsized by used the text instead of broken in the middle.
     */
    fun setEllipsizeText(ellipsizeText: CharSequence?) {
        this.mEllipsizeText = ellipsizeText
    }

    class Range<T : Comparable<T>?>(val lower: T, val upper: T) {
        init {
            require(lower!!.compareTo(upper) <= 0) { "lower must be less than or equal to upper" }
        }

        fun contains(value: T): Boolean {
            val gteLower = value!!.compareTo(lower) >= 0
            val lteUpper = value.compareTo(upper) < 0

            return gteLower && lteUpper
        }
    }

    companion object {
        private const val DEFAULT_ELLIPSIZE_TEXT = "..."
    }
}

interface OnEndTextClickListener {
    fun click()
}